home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue46 / Alfresco / AAExpr.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-04-25  |  15.5 KB  |  506 lines

  1. {*********************************************************}
  2. {* AAExpr                                                *}
  3. {* Copyright (c) Julian M Bucknall 1998-1999             *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Expression parser and evaluator                       *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AAExpr;
  14.  
  15. interface
  16.  
  17. uses
  18.   SysUtils,
  19.   AAChStk,
  20.   AAStStk,
  21.   AAVarLst;
  22.  
  23. type
  24.   TaaExprTokenType = ( {Expression token types}
  25.          ttOperator,   {..an operator}
  26.          ttNumOperand, {..a numeric operand}
  27.          ttVarOperand, {..an operand that's a variable}
  28.          ttEndOfExpr); {..the end of the expression}
  29.  
  30. type
  31.   TaaExprParserState = ( {Possible parser states}
  32.      psCannotBeOperand,  {..the next token cannot be an operand}
  33.      psCouldBeOperand,   {..the next token could be an operand}
  34.      psMustBeOperand);   {..the next token must an operand or '('}
  35.  
  36. type
  37.   TaaExpressionParser = class
  38.     private
  39.       FExpr     : PChar;
  40.       FOrigExpr : PChar;
  41.       FParsed   : boolean;
  42.       FStStack  : TaaStringStack;
  43.       FOpStack  : TaaCharacterStack;
  44.       FVarList  : TaaVariableList;
  45.     protected
  46.       function epGetExpression : string;
  47.       function epGetRPNExpression : string;
  48.       function epGetValue : double;
  49.       function epGetVariable(const aName : string) : double;
  50.       procedure epSetExpression(aExpr : string);
  51.       procedure epSetVariable(const aName : string; aValue : double);
  52.  
  53.       procedure epRaiseBadExpressionError(aPosn : PChar);
  54.  
  55.       procedure epCheckBadParserState(aState    : TaaExprParserState;
  56.                                       aBadState : TaaExprParserState;
  57.                                       aCharPos  : PChar);
  58.       procedure epFindEndOfNumber;
  59.       procedure epFindEndOfIdentifier;
  60.       procedure epFormRPNSubExpr(aOp : char; aCharPos : PChar);
  61.       function epGetNextToken(var aStartToken : PChar) : TaaExprTokenType;
  62.       function epGetPrecedence(aOp : char) : integer;
  63.       procedure epParseToRPN;
  64.       procedure epPushNewOperand(aStartPos : PChar);
  65.       procedure epSkipBlanks;
  66.  
  67.     public
  68.       constructor Create(const aExpr : string);
  69.       destructor Destroy; override;
  70.  
  71.       {$IFOPT D+}
  72.       procedure TokenPrint;
  73.       {$ENDIF}
  74.  
  75.       property Expression : string
  76.          read epGetExpression write epSetExpression;
  77.       property RPNExpression : string
  78.          read epGetRPNExpression;
  79.       property Value : double
  80.          read epGetValue;
  81.       property Variable[const aName : string] : double
  82.          read epGetVariable write epSetVariable;
  83.   end;
  84.  
  85. implementation
  86.  
  87. uses
  88.   AAFltStk;
  89.  
  90. const
  91.   OperatorSet = ['(', ')', '^', '*', '/', '+', '-'];
  92.   NumberSet = ['0'..'9', '.'];
  93.   IdentifierSet = ['A'..'Z', 'a'..'z', '0'..'9', '_'];
  94.  
  95. const
  96.   UnaryMinus = char(ord('-') or $80);
  97.  
  98. {===Helper functions=================================================}
  99. function Power(X, Y : double) : double;
  100. begin
  101.   if (Y = 0.0) then
  102.     Result := 1.0
  103.   else if (Y = 1.0) then
  104.     Result := X
  105.   else
  106.     Result := exp(ln(X) * Y);
  107. end;
  108. {====================================================================}
  109.  
  110.  
  111. {===TaaExpressionParser==============================================}
  112. constructor TaaExpressionParser.Create(const aExpr : string);
  113. begin
  114.   inherited Create;
  115.   {create a string stack for the operands and an operator stack}
  116.   FStStack := TaaStringStack.Create(4096);
  117.   FOpStack := TaaCharacterStack.Create;
  118.   {create a variable list}
  119.   FVarList := TaaVariableList.Create;
  120.   {set the expression string}
  121.   Expression := aExpr;
  122. end;
  123. {--------}
  124. destructor TaaExpressionParser.Destroy;
  125. begin
  126.   Expression := '';
  127.   FStStack.Free;
  128.   FOpStack.Free;
  129.   FVarList.Free;
  130.   inherited Destroy;
  131. end;
  132. {--------}
  133. procedure TaaExpressionParser.epCheckBadParserState(
  134.                                 aState    : TaaExprParserState;
  135.                                 aBadState : TaaExprParserState;
  136.                                 aCharPos  : PChar);
  137. begin
  138.   if (aState = aBadState) then
  139.     epRaiseBadExpressionError(aCharPos);
  140. end;
  141. {--------}
  142. procedure TaaExpressionParser.epFindEndOfNumber;
  143. var
  144.   TempExpr : PChar;
  145. begin
  146.   {assume that FExpr is a digit, find the end of the stream of digits}
  147.   TempExpr := FExpr;
  148.   while (TempExpr^ in NumberSet) do
  149.     inc(TempExpr);
  150.   FExpr := TempExpr;
  151. end;
  152. {--------}
  153. procedure TaaExpressionParser.epFindEndOfIdentifier;
  154. var
  155.   TempExpr : PChar;
  156. begin
  157.   {assume that FExpr is an alphanum char, find the end of the stream
  158.    of alphanum chars}
  159.   TempExpr := FExpr;
  160.   while (TempExpr^ in IdentifierSet) do
  161.     inc(TempExpr);
  162.   FExpr := TempExpr;
  163. end;
  164. {--------}
  165. procedure TaaExpressionParser.epFormRPNSubExpr(aOp      : char;
  166.                                                aCharPos : PChar);
  167. var
  168.   PrecOp   : integer;
  169.   PrecTop  : integer;
  170.   TempOp   : char;
  171.   Operand1 : string[255];
  172.   Operand2 : string[255];
  173. begin
  174.   {this routine is called when the operator about to be pushed, aOp,
  175.    has a precdence lower than the operator on top of the operator
  176.    stack. We need to pop off some operators and operands and form some
  177.    RPN expressions to push onto the operand stack, until the operator
  178.    stack is exhausted or the top operator has a precedence value less
  179.    than or equal to the given operator's precedence value.}
  180.   PrecOp := epGetPrecedence(aOp);
  181.   PrecTop := epGetPrecedence(FOpStack.Examine);
  182.   while (PrecOp < PrecTop) do begin
  183.     TempOp := FOpStack.Pop;
  184.     if (TempOp = UnaryMinus) then begin
  185.       if (FStStack.Count = 0) then
  186.         epRaiseBadExpressionError(aCharPos);
  187.       Operand1 := FStStack.Pop + UnaryMinus;
  188.       FStStack.Push(Operand1);
  189.     end
  190.     else begin
  191.       if (FStStack.Count < 2) then
  192.         epRaiseBadExpressionError(aCharPos);
  193.       Operand2 := FStStack.Pop;
  194.       Operand1 := FStStack.Pop + Operand2 + TempOp;
  195.       FStStack.Push(Operand1);
  196.     end;
  197.     if FOpStack.IsEmpty then
  198.       PrecOp := 0
  199.     else
  200.       PrecTop := epGetPrecedence(FOpStack.Examine);
  201.   end;
  202.   {if the given operator was a right parenthesis the top of the
  203.    operator stack *must* be a left parenthesis and we should remove
  204.    it}
  205.   if (aOp = ')') then begin
  206.     if FOpStack.IsEmpty or (FOpStack.Examine <> '(') then
  207.       epRaiseBadExpressionError(aCharPos);
  208.     FOpStack.Pop;
  209.   end;
  210. end;
  211. {--------}
  212. function TaaExpressionParser.epGetExpression : string;
  213. begin
  214.   Result := StrPas(FOrigExpr);
  215. end;
  216. {--------}
  217. function TaaExpressionParser.epGetNextToken(var aStartToken : PChar)
  218.                                                    : TaaExprTokenType;
  219. var
  220.   CurChar : char;
  221. begin
  222.   epSkipBlanks;
  223.   aStartToken := FExpr;
  224.   CurChar := aStartToken^;
  225.   if (CurChar = #0) then
  226.     Result := ttEndOfExpr
  227.   else if (CurChar in OperatorSet) then begin
  228.     inc(FExpr); {operators are always one character in size}
  229.     Result := ttOperator;
  230.   end
  231.   else if (CurChar in NumberSet) then begin
  232.     epFindEndOfNumber;
  233.     Result := ttNumOperand;
  234.   end
  235.   else if (CurChar in IdentifierSet) then begin
  236.     epFindEndOfIdentifier;
  237.     Result := ttVarOperand;
  238.   end
  239.   else begin
  240.     Result := ttEndOfExpr;
  241.     epRaiseBadExpressionError(aStartToken);
  242.   end;
  243. end;
  244. {--------}
  245. function TaaExpressionParser.epGetPrecedence(aOp : char) : integer;
  246. const
  247.   Operators : string[8] = '()^*/+-' + UnaryMinus;
  248.   Precedences : array [1..8] of byte = (1,1,7,5,5,3,3,9);
  249. var
  250.   Posn : integer;
  251. begin
  252.   Posn := Pos(aOp, Operators);
  253.   Result := Precedences[Posn];
  254. end;
  255. {--------}
  256. function TaaExpressionParser.epGetRPNExpression : string;
  257. begin
  258.   if not FParsed then
  259.     epParseToRPN;
  260.   Result := FStStack.Examine;
  261. end;
  262. {--------}
  263. function TaaExpressionParser.epGetValue : double;
  264. var
  265.   DblStack : TaaFloatStack;
  266.   i        : integer;
  267.   Operand1 : double;
  268.   Operand2 : double;
  269.   Expr     : string[255];
  270.   OperandSt: string[255];
  271. begin
  272.   if not FParsed then
  273.     epParseToRPN;
  274.   {prepare a stack for doubles}
  275.   DblStack := TaaFloatStack.Create;
  276.   try
  277.     {read through the RPN expression and evaluate it}
  278.     Expr := FStStack.Examine;
  279.     i := 0;
  280.     while (i < length(Expr)) do begin
  281.       inc(i);
  282.       if (Expr[i] = ' ') then begin
  283.         if Expr[i+1] in NumberSet then begin
  284.           OperandSt := '';
  285.           while Expr[i+1] in NumberSet do begin
  286.             OperandSt := OperandSt + Expr[i+1];
  287.             inc(i);
  288.           end;
  289.           DblStack.Push(StrToFloat(OperandSt));
  290.         end
  291.         else begin
  292.           OperandSt := '';
  293.           while Expr[i+1] in IdentifierSet do begin
  294.             OperandSt := OperandSt + Expr[i+1];
  295.             inc(i);
  296.           end;
  297.           DblStack.Push(FVarList.Value[OperandSt]);
  298.         end
  299.       end
  300.       else begin
  301.         if Expr[i] = UnaryMinus then
  302.           DblStack.Push(-DblStack.Pop)
  303.         else begin
  304.           Operand2 := DblStack.Pop;
  305.           Operand1 := DblStack.Pop;
  306.           case Expr[i] of
  307.             '+' : DblStack.Push(Operand1 + Operand2);
  308.             '-' : DblStack.Push(Operand1 - Operand2);
  309.             '*' : DblStack.Push(Operand1 * Operand2);
  310.             '/' : DblStack.Push(Operand1 / Operand2);
  311.             '^' : DblStack.Push(Power(Operand1, Operand2));
  312.           end;{case}
  313.         end;
  314.       end;
  315.     end;
  316.     Result := DblStack.Pop;
  317.   finally
  318.     DblStack.Free;
  319.   end;
  320. end;
  321. {--------}
  322. function TaaExpressionParser.epGetVariable(const aName : string) : double;
  323. begin
  324.   Result := FVarList.Value[aName];
  325. end;
  326. {--------}
  327. procedure TaaExpressionParser.epParseToRPN;
  328. var
  329.   ParserState : TaaExprParserState;
  330.   TokenType   : TaaExprTokenType;
  331.   Op          : char;
  332.   StartPos    : PChar;
  333.   PrecOp      : integer;
  334.   PrecTop     : integer;
  335. begin
  336.   {if we've done this already, get out}
  337.   if FParsed then
  338.     Exit;
  339.   {initialise the operator stack to have a left parenthesis; when we
  340.    reach the end of the expression we'll be pretending it has a right
  341.    parenthesis}
  342.   FOpStack.Clear;
  343.   FOpStack.Push('(');
  344.   {initialise the operand stack}
  345.   FStStack.Clear;
  346.   {initialise the parser}
  347.   FExpr := FOrigExpr;
  348.   ParserState := psCouldBeOperand;
  349.   {get the next token from the expression}
  350.   TokenType := epGetNextToken(StartPos);
  351.   {process all the tokens}
  352.   while (TokenType <> ttEndOfExpr) do begin
  353.     {what type of token are we trying to parse?}
  354.     case TokenType of
  355.       ttOperator :
  356.         begin
  357.           {it's an operator}
  358.           Op := StartPos^;
  359.           {if the operator is a left parenthesis, just push it onto
  360.            the operator stack}
  361.           if (Op = '(') then begin
  362.             FOpStack.Push(Op);
  363.             ParserState := psCouldBeOperand;
  364.           end
  365.           else begin
  366.             epCheckBadParserState(ParserState, psMustBeOperand, StartPos);
  367.             {if the operator is a right parenthesis, start popping off
  368.              operators and operands and forming RPN subexpressions,
  369.              until we reach a left parenthesis}
  370.             if (Op = ')') then begin
  371.               if FOpStack.IsEmpty then
  372.                 epRaiseBadExpressionError(StartPos);
  373.               epFormRPNSubExpr(')', StartPos);
  374.               ParserState := psCannotBeOperand;
  375.             end
  376.             {if the operator is a unary operator, then ignore a unary
  377.              plus (it has no effect) and push a unary minus}
  378.             else if (ParserState = psCouldBeOperand) then begin
  379.               if (Op <> '+') and (Op <> '-') then
  380.                 epRaiseBadExpressionError(StartPos);
  381.               if (Op = '-') then
  382.                 FOpStack.Push(UnaryMinus);
  383.               ParserState := psMustBeOperand;
  384.             end
  385.             {if we reach this point, the operator must be pushed onto
  386.              the stack, however, we first need to check that we are not
  387.              pushing it onto an operator of greater precedence}
  388.             else begin
  389.               PrecOp := epGetPrecedence(Op);
  390.               if FOpStack.IsEmpty then
  391.                 PrecTop := 0
  392.               else
  393.                 PrecTop := epGetPrecedence(FOpStack.Examine);
  394.               if (PrecOp < PrecTop) then
  395.                 epFormRPNSubExpr(Op, StartPos);
  396.               FOpStack.Push(Op);
  397.               ParserState := psCouldBeOperand;
  398.             end;
  399.           end;
  400.         end;
  401.       ttNumOperand,
  402.       ttVarOperand :
  403.         begin
  404.           {it's an operand}
  405.           epCheckBadParserState(ParserState, psCannotBeOperand, StartPos);
  406.           epPushNewOperand(StartPos);
  407.           ParserState := psCannotBeOperand;
  408.         end;
  409.     end;
  410.     {get the next token from the expression}
  411.     TokenType := epGetNextToken(StartPos);
  412.   end;
  413.   {at the end we pretend that the expression was terminated with a
  414.    right parenthesis and we can't be expecting an operand}
  415.   epCheckBadParserState(ParserState, psMustBeOperand, StartPos);
  416.   epFormRPNSubExpr(')', StartPos);
  417.   {at this point, the operator stack should be empty and the operand
  418.    stack should have one item: the RPN of the original expression}
  419.   if (not FOpStack.IsEmpty) or (FStStack.Count <> 1) then
  420.     epRaiseBadExpressionError(StartPos);
  421.   FParsed := true;
  422. end;
  423. {--------}
  424. procedure TaaExpressionParser.epPushNewOperand(aStartPos : PChar);
  425. var
  426.   TempStr : string[255];
  427. begin
  428.   TempStr[0] := char(succ(FExpr - aStartPos));
  429.   TempStr[1] := ' ';
  430.   Move(aStartPos^, TempStr[2], FExpr - aStartPos);
  431.   FStStack.Push(TempStr);
  432. end;
  433. {--------}
  434. procedure TaaExpressionParser.epRaiseBadExpressionError(aPosn : PChar);
  435. begin
  436.   if (aPosn = StrEnd(FOrigExpr)) then
  437.     raise Exception.Create(
  438.        'Badly formed expression detected at end of string')
  439.   else
  440.     raise Exception.Create(
  441.        Format('Badly formed expression with character [%s], at position %d',
  442.               [aPosn^, succ(aPosn - FOrigExpr)]));
  443. end;
  444. {--------}
  445. procedure TaaExpressionParser.epSetExpression(aExpr : string);
  446. begin
  447.   {first destroy the original expression}
  448.   if (FOrigExpr <> nil) then
  449.     StrDispose(FOrigExpr);
  450.   {now allocate the new one}
  451.   if (aExpr = '') then
  452.     FOrigExpr := nil
  453.   else begin
  454.     if (length(aExpr) > 255) then
  455.       raise Exception.Create('TaaExpressionParser: the expression is too long');
  456.     FOrigExpr := StrAlloc(succ(length(aExpr)));
  457.     StrPCopy(FOrigExpr, aExpr);
  458.   end;
  459.   {the expression is not yet parsed}
  460.   FParsed := aExpr = '';
  461. end;
  462. {--------}
  463. procedure TaaExpressionParser.epSetVariable(const aName : string; aValue : double);
  464. begin
  465.   FVarList.Value[aName] := aValue
  466. end;
  467. {--------}
  468. procedure TaaExpressionParser.epSkipBlanks;
  469. var
  470.   TempExpr : PChar;
  471. begin
  472.   {jump past all the blanks}
  473.   TempExpr := FExpr;
  474.   while (TempExpr^ = ' ') do
  475.     inc(TempExpr);
  476.   FExpr := TempExpr;
  477. end;
  478. {--------}
  479. {$IFOPT D+}
  480. procedure TaaExpressionParser.TokenPrint;
  481. var
  482.   i         : integer;
  483.   StartPos  : PChar;
  484.   TokenType : TaaExprTokenType;
  485. begin
  486.   FExpr := FOrigExpr;
  487.   TokenType := epGetNextToken(StartPos);
  488.   while TokenType <> ttEndOfExpr do begin
  489.     case TokenType of
  490.       ttOperator   : write('  operator:         ');
  491.       ttNumOperand : write('  number operand:   ');
  492.       ttVarOperand : write('  variable operand: ');
  493.     end;{case}
  494.     for i := 0 to pred(FExpr-StartPos) do
  495.       write((StartPos + i)^);
  496.     writeln;
  497.     TokenType := epGetNextToken(StartPos);
  498.   end;
  499.   writeln('  end of expression');
  500. end;
  501. {$ENDIF}
  502. {====================================================================}
  503.  
  504. end.
  505.  
  506.